import argparse
import glob
import json
import os
import re
import shutil
import signal
import socket
import subprocess
import time
import traceback
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path

import psutil
from flask import Flask, jsonify, request
from flask_cors import CORS
from requests import post
from utils.modelget import ModelDownloader
from utils.pyget import PyDownloader

app = Flask(__name__)
CORS(app)

"""
使用10000到12000端口号

需要配置三个提交接口
"""
# 运行前如果未设置DEPLOY_MODE，则默认使用GPU模式
if "DEPLOY_MODE" not in os.environ:
    os.environ["DEPLOY_MODE"] = "GPU"

# 环境变量配置：CPU 或 GPU（CCR镜像）
# 从环境变量读取，默认为 'CPU'
DEPLOY_MODE = os.environ.get('DEPLOY_MODE', 'CPU').upper()
if DEPLOY_MODE not in ['CPU', 'GPU']:
    print(f"⚠️  警告: DEPLOY_MODE={DEPLOY_MODE} 无效，使用默认值 CPU")
    DEPLOY_MODE = 'CPU'

print(f"🔧 部署模式: {DEPLOY_MODE}")

# CV任务类型配置字典（视觉任务）
CV_TASK_CONFIG = {
    'classification': {
        'name': '视觉分类服务',
        'env_var': 'MODEL_PATH',
        'api_path': '/api/v1/classify/single',
        'api_name': '分类',
        'code_dir': 'deploy_code_cv',      # 代码文件下载目录
        'model_dir': 'weights'        # 模型文件下载目录
    },
    'segmentation': {
        'name': '视觉分割服务',
        'env_var': 'SEGMENT_MODEL_PATH',
        'api_path': '/api/v1/segment/single',
        'api_name': '分割',
        'code_dir': 'deploy_code_cv',      # 代码文件下载目录
        'model_dir': 'weights'        # 模型文件下载目录
    }
    # 后续可以扩展 detect 等类型
    # 'detection': {
    #     'name': '检测服务',
    #     'env_var': 'DETECT_MODEL_PATH',
    #     'api_path': '/api/v1/detect/single',
    #     'api_name': '检测',
    #     'code_dir': 'deploy_code_cv',
    #     'model_dir': 'weights'
    # }
}

# 数值任务类型配置字典
NUMERIC_TASK_CONFIG = {
    'regression': {
        'name': '数值回归服务',
        'env_var': 'MODEL_PATH',
        'api_path': '/predict',
        'api_name': '回归',
        'code_dir': 'deploy_code_data',   # 数值任务代码文件下载目录
        'model_dir': 'save_model'      # 数值任务模型文件下载目录
    },
    "classification": {
        'name': '数值分类服务',
        'env_var': 'MODEL_PATH',
        'api_path': '/predict',
        'api_name': '数值分类',
        'code_dir': 'deploy_code_data',   # 数值任务代码文件下载目录
        'model_dir': 'save_model'      # 数值任务模型文件下载目录
    }
}

# 合并所有任务配置（用于统一验证和查找）
ALL_TASK_CONFIG = {**CV_TASK_CONFIG, **NUMERIC_TASK_CONFIG}

# 配置接口提交地址（基础URL，根据任务类型动态生成）
BASE_CALLBACK_URL = 'http://172.28.99.91:10001/callback'


def get_task_config_by_app_type(task_type, app_type=None):
    """
    根据 appType 和 task_type 获取对应的任务配置
    参数:
        task_type: 任务类型（如 'classification', 'segmentation', 'regression'）
        app_type: 应用类型，'cv' 表示视觉任务，'data' 表示数值任务
    返回:
        对应的任务配置字典，如果找不到则返回 None
    """
    # 如果指定了 appType，直接根据 appType 选择配置
    if app_type == 'cv':
        return CV_TASK_CONFIG.get(task_type)
    elif app_type == 'data':
        return NUMERIC_TASK_CONFIG.get(task_type)
    
    # 如果没有指定 appType，为了向后兼容，根据 task_type 判断
    # 如果 task_type 在 CV_TASK_CONFIG 中，优先使用 CV
    if task_type in CV_TASK_CONFIG:
        return CV_TASK_CONFIG.get(task_type)
    elif task_type in NUMERIC_TASK_CONFIG:
        return NUMERIC_TASK_CONFIG.get(task_type)
    
    return None


class CustomResponse:
    status_map = {
        200: '成功%s',
        204: '失败， %s',
        500: '服务异常，%s'

    }

    def __init__(self, status=200, data=None, suffix=''):
        self.data = data or {}
        self.suffix = suffix
        self.status = status

    @property
    def result_data(self):
        return {
            'status': self.status,
            'message': self.status_map.get(self.status, '') % self.suffix,
            'data': self.data
        }


def submit_api(service_tag, data, task_type='classification', app_type=None):
    """
    提交接口
    根据任务类型动态生成回调URL：
    - 视觉任务（CV）：使用 /callback/cv/deploy/
    - 数值任务（Numeric）：使用 /callback/numeric/deploy/
    
    参数:
        service_tag: 服务标签 ('start', 'stop', 'delete')
        data: 要提交的数据
        task_type: 任务类型，默认为 'classification'
        app_type: 应用类型，'cv' 表示视觉任务，'data' 表示数值任务
    """
    # 根据 appType 判断回调类型：cv -> 'cv'，data -> 'numeric'
    if app_type == 'cv':
        callback_type = 'cv'
    elif app_type == 'data':
        callback_type = 'numeric'
    else:
        callback_type = 'cv'  # 默认使用 cv（向后兼容）
    
    # 根据 service_tag 生成对应的URL
    if service_tag == 'start':
        url = f'{BASE_CALLBACK_URL}/{callback_type}/deploy/start/'
    elif service_tag == 'stop':
        url = f'{BASE_CALLBACK_URL}/{callback_type}/deploy/stop/'
    elif service_tag == 'delete':
        url = f'{BASE_CALLBACK_URL}/{callback_type}/deploy/remove/'
    else:
        url = None

    # 如接口存在，进行提交
    if url:
        try:
            print(f'📤 回调URL ({callback_type}): {url}')
            resp = post(url, json=data, timeout=5)
            print('写入成功:', resp)
        except Exception as e:
            resp = CustomResponse(status=204, suffix=str(e)).result_data
    else:
        resp = CustomResponse(status=204, suffix='未配置提交接口').result_data
    return resp


def is_port_in_use(port):
    """
    检查端口是否已被占用
    """
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(1)  # 设置超时时间
            result = s.connect_ex(('localhost', port))
            return result == 0
    except Exception as e:
        print(f"⚠️ 检查端口 {port} 时发生错误: {e}")
        return False


def parse_cuda_device(cuda_str):
    """
    解析cuda设备字符串，提取GPU ID
    参数:
        cuda_str: cuda设备字符串，如 "cuda:0", "cuda:1" 等
    返回:
        (use_gpu, gpu_device_ids, cuda_visible_devices)
        - use_gpu: "true" 或 "false"
        - gpu_device_ids: JSON数组格式的字符串，如 '["0"]'，表示宿主机GPU编号
        - cuda_visible_devices: 容器内GPU编号，如 "0"，从0开始
    """
    if not cuda_str:
        return "false", "[]", ""
    
    # 解析cuda字符串，支持格式：cuda:0, cuda:1, cuda:0,1 等
    # 提取GPU ID部分
    if cuda_str.startswith('cuda:'):
        gpu_ids_str = cuda_str[5:]  # 去掉 "cuda:" 前缀
    else:
        # 如果不是cuda:开头，尝试直接解析为数字
        gpu_ids_str = cuda_str
    
    # 支持逗号或空格分隔的多个GPU ID
    gpu_ids = []
    for gpu_id in re.split(r'[,，\s]+', gpu_ids_str):
        gpu_id = gpu_id.strip()
        if gpu_id and gpu_id.isdigit():
            gpu_ids.append(int(gpu_id))
    
    if not gpu_ids:
        return "false", "[]", ""
    
    # 生成GPU_DEVICE_IDS（宿主机GPU编号，JSON数组格式）
    gpu_device_ids_json = json.dumps([str(gpu_id) for gpu_id in gpu_ids])
    
    # 生成CUDA_VISIBLE_DEVICES（容器内GPU编号，从0开始）
    # Docker会将宿主机GPU重新映射为容器内从0开始编号
    # 例如：device_ids: ["1"] → 容器内只有cuda:0 → CUDA_VISIBLE_DEVICES=0
    # 例如：device_ids: ["0","1"] → 容器内有cuda:0,cuda:1 → CUDA_VISIBLE_DEVICES=0,1
    container_gpu_ids = [str(i) for i in range(len(gpu_ids))]
    cuda_visible_devices = ",".join(container_gpu_ids)
    
    return "true", gpu_device_ids_json, cuda_visible_devices


def get_address():
    """
    获取本机所有网络接口的IP地址和MAC地址等信息
    """
    ip_all = {}
    net_io_counters = psutil.net_if_addrs()
    for interface, addrs in net_io_counters.items():
        for addr in addrs:
            if addr.family == socket.AF_INET:  # IPv4地址
                # print(f'接口 {interface} 的 IP 地址: {addr.address}')
                ip_all.update({interface: addr.address})
    # print(ip_all)
    ip = ip_all.get('eno2', '')
    return ip


def get_docker_config(port, task_type='classification', app_type=None):
    """
    根据部署模式和任务类型获取Docker配置
    参数:
        port: 端口号
        task_type: 任务类型，默认为 'classification'
        app_type: 应用类型，'cv' 表示视觉任务，'data' 表示数值任务
    返回: (IMAGE_NAME, CONTAINER_NAME, COMPOSE_FILE, COMPOSE_TEMPLATE, DOCKERFILE)
    """
    # 根据 appType 判断是视觉任务还是数值任务
    if app_type == 'cv':
        is_cv_task = True
        is_numeric_task = False
    elif app_type == 'data':
        is_cv_task = False
        is_numeric_task = True
    else:
        # 向后兼容：根据 task_type 判断
        is_cv_task = task_type in CV_TASK_CONFIG
        is_numeric_task = task_type in NUMERIC_TASK_CONFIG
    
    if is_cv_task:
        # 视觉任务（CV）配置
        if DEPLOY_MODE == 'CPU':
            # CPU模式配置
            IMAGE_NAME = "devdeploy-cv-api-cpu:latest"
            CONTAINER_NAME = f"devdeploy-cv-{task_type}-cpu-{port}"
            COMPOSE_FILE = f"docker-compose.cpu.{task_type}.{port}.yml"
            COMPOSE_TEMPLATE = "docker-compose.cv.cpu.template.yml"
            DOCKERFILE = "Dockerfile.cpu"
        else:
            # GPU模式配置（CCR镜像）
            IMAGE_NAME = "devdeploy-cv-api:latest"
            CONTAINER_NAME = f"devdeploy-cv-{task_type}-{port}"
            COMPOSE_FILE = f"docker-compose.{task_type}.{port}.yml"
            COMPOSE_TEMPLATE = "docker-compose.cv.template.yml"
            DOCKERFILE = "Dockerfile.ccr"
    elif is_numeric_task:
        # 数值任务（Numeric）配置 - 使用CPU模式
        IMAGE_NAME = "devdeploy-numeric-api-cpu:latest"
        CONTAINER_NAME = f"devdeploy-numeric-{task_type}-cpu-{port}"
        COMPOSE_FILE = f"docker-compose.cpu.{task_type}.{port}.yml"
        COMPOSE_TEMPLATE = "docker-compose.numeric.cpu.template.yml"
        DOCKERFILE = "Dockerfile"  # 数值任务使用统一的Dockerfile
    else:
        # 未知任务类型，直接抛出异常
        raise ValueError(f'❌ 不支持的任务类型: {task_type}。支持的任务类型: {list(ALL_TASK_CONFIG.keys())}')
    
    return IMAGE_NAME, CONTAINER_NAME, COMPOSE_FILE, COMPOSE_TEMPLATE, DOCKERFILE


def download_files(py_url, model_url, folder, service_id, service_tag, task_type='classification', app_type=None):
    """
    下载代码文件和模型文件
    根据任务类型选择不同的下载路径：
    - 视觉任务（CV）：代码文件 -> deploy_code_cv，模型文件 -> weights
    - 数值任务（Numeric）：代码文件 -> deploy_code_data，模型文件 -> save_model
    
    参数:
        py_url: 代码文件下载URL
        model_url: 模型文件下载URL
        folder: 服务文件夹名称
        service_id: 服务ID
        service_tag: 服务标签
        task_type: 任务类型，默认为 'classification'
        app_type: 应用类型，'cv' 表示视觉任务，'data' 表示数值任务
    
    返回: (py_path, model_path) 或 (None, None) 如果失败
    """
    # 根据 appType 获取对应的任务配置
    task_config = get_task_config_by_app_type(task_type, app_type)
    
    # 如果找不到配置，尝试使用默认值
    if task_config is None:
        if task_type not in ALL_TASK_CONFIG:
            print(f'⚠️  警告: taskType={task_type} 无效，使用默认值 classification')
            task_type = 'classification'
        task_config = get_task_config_by_app_type(task_type, app_type)
        if task_config is None:
            # 如果还是找不到，使用 ALL_TASK_CONFIG 作为后备
            task_config = ALL_TASK_CONFIG.get(task_type, ALL_TASK_CONFIG['classification'])
    
    code_dir = task_config['code_dir']
    model_dir = task_config['model_dir']
    
    print(f'📥 开始下载代码文件: {py_url} -> {code_dir}')
    py_path = PyDownloader(py_url, 'server' + '/' + folder + '/' + code_dir).download
    print(f'📥 开始下载模型文件: {model_url} -> {model_dir}')
    model_path = ModelDownloader(model_url, 'server' + '/' + folder + '/' + model_dir).download
    print(f'model_path:{model_path}')
    print(f'py_path:{py_path}')

    # 检查下载是否成功
    if not py_path:
        print(f'❌ 代码文件下载失败: {py_url}')
        return_info = CustomResponse(status=204,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=f'代码文件下载失败，请检查URL是否可访问: {py_url}').result_data
        print('回调写入:', return_info)
        submit_api(service_tag, return_info, task_type, app_type)
        return None, None
    
    if not model_path:
        print(f'❌ 模型文件下载失败: {model_url}')
        return_info = CustomResponse(status=204,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=f'模型文件下载失败，请检查URL是否可访问: {model_url}').result_data
        print('回调写入:', return_info)
        submit_api(service_tag, return_info, task_type, app_type)
        return None, None
    
    return py_path, model_path


def prepare_directories(folder, service_id, task_type='classification', app_type=None):
    """
    准备目录并切换到代码目录
    根据任务类型选择不同的代码目录：
    - 视觉任务（CV）：deploy_code_cv
    - 数值任务（Numeric）：deploy_code_data
    
    参数:
        folder: 服务文件夹名称
        service_id: 服务ID
        task_type: 任务类型，默认为 'classification'
        app_type: 应用类型，'cv' 表示视觉任务，'data' 表示数值任务
    
    返回: (server_dir, code_dir, original_cwd) 或 None 如果失败
    """
    # 根据 appType 获取对应的任务配置
    task_config = get_task_config_by_app_type(task_type, app_type)
    
    # 如果找不到配置，尝试使用默认值
    if task_config is None:
        if task_type not in ALL_TASK_CONFIG:
            task_type = 'classification'
        task_config = get_task_config_by_app_type(task_type, app_type)
        if task_config is None:
            # 如果还是找不到，使用 ALL_TASK_CONFIG 作为后备
            task_config = ALL_TASK_CONFIG.get(task_type, ALL_TASK_CONFIG['classification'])
    
    code_dir_name = task_config['code_dir']
    
    dir_cmd = Path(__file__).parent / 'server' / folder
    server_dir = dir_cmd.resolve()
    code_dir = (dir_cmd / code_dir_name).resolve()
    
    try:
        if not code_dir.exists():
            print(f'❌ {code_dir_name}目录不存在: {code_dir}')
            raise FileNotFoundError(f'{code_dir_name}目录不存在: {code_dir}')
        
        original_cwd = os.getcwd()
        os.chdir(code_dir)
        print(f'current directory: {os.getcwd()}')
        return server_dir, code_dir, original_cwd
    except FileNotFoundError as e:
        print(f'no exist:{e}')
        return None


def find_model_file(model_path, server_dir, service_id, task_type='classification', app_type=None):
    """
    查找模型文件并计算容器内路径
    根据任务类型查找不同的模型文件：
    - CV任务（视觉任务）：查找 .onnx 文件
    - 数值任务：查找 .joblib 文件
    
    参数:
        model_path: 模型目录路径
        server_dir: 服务器根目录
        service_id: 服务ID
        task_type: 任务类型，默认为 'classification'
        app_type: 应用类型，'cv' 表示视觉任务，'data' 表示数值任务
    
    返回: model_path_in_container 或 None 如果失败
    """
    # 根据 appType 和 task_type 确定文件扩展名
    if app_type == 'cv':
        # CV任务：查找 .onnx 文件
        file_extension = '*.onnx'
        file_type_name = 'ONNX'
    elif app_type == 'data':
        # 数值任务：查找 .joblib 文件
        file_extension = '*.joblib'
        file_type_name = 'Joblib'
    elif task_type in CV_TASK_CONFIG:
        # CV任务：查找 .onnx 文件（向后兼容）
        file_extension = '*.onnx'
        file_type_name = 'ONNX'
    elif task_type in NUMERIC_TASK_CONFIG:
        # 数值任务：查找 .joblib 文件（向后兼容）
        file_extension = '*.joblib'
        file_type_name = 'Joblib'
    else:
        # 默认使用 .onnx（向后兼容）
        file_extension = '*.onnx'
        file_type_name = 'ONNX'
        print(f'⚠️  未知任务类型 {task_type}，默认查找 .onnx 文件')
    
    model_dir = Path(model_path).resolve()
    model_files = list(model_dir.rglob(file_extension))
    
    if not model_files:
        print(f'❌ 未找到模型文件 ({file_extension}) 在: {model_dir}')
        return None
    
    model_file = model_files[0]
    print(f'📦 找到{file_type_name}模型文件: {model_file}')
    
    model_file_abs = Path(model_file).resolve()
    try:
        model_relative_path = model_file_abs.relative_to(server_dir)
    except ValueError as e:
        print(f'❌ 路径计算错误: {e}')
        print(f'   model_file_abs: {model_file_abs}')
        print(f'   server_dir: {server_dir}')
        return None
    
    model_relative_path_str = '../' + str(model_relative_path).replace('\\', '/')
    print(f'model_relative_path_str:{model_relative_path_str}')
    
    model_path_in_container = f"/app/{model_relative_path_str[3:]}"
    return model_path_in_container


def generate_compose_file(compose_template, compose_file, image_name, container_name, 
                         port, ssh_host_port, model_path_in_container, task_type, service_id,
                         use_gpu="false", gpu_device_ids="[]", cuda_visible_devices="", app_type=None):
    """
    生成docker-compose配置文件
    参数:
        use_gpu: "true" 或 "false"
        gpu_device_ids: JSON数组格式的字符串，如 '["0"]'，表示宿主机GPU编号
        cuda_visible_devices: 容器内GPU编号，如 "0"
        app_type: 应用类型，'cv' 表示视觉任务，'data' 表示数值任务
    返回: True 成功, False 失败
    """
    try:
        template_path = Path(compose_template)
        if not template_path.exists():
            raise FileNotFoundError(f'模板文件不存在: {compose_template}')
        
        with open(template_path, 'r', encoding='utf-8') as f:
            compose_content = f.read()
        
        # 替换占位符
        compose_content = compose_content.replace('{{IMAGE_NAME}}', image_name)
        compose_content = compose_content.replace('{{CONTAINER_NAME}}', container_name)
        compose_content = compose_content.replace('{{PORT}}', str(port))
        compose_content = compose_content.replace('{{SSH_HOST_PORT}}', str(ssh_host_port))
        
        # 根据 appType 获取对应的任务配置
        task_config = get_task_config_by_app_type(task_type, app_type)
        
        # 如果找不到配置，尝试使用默认值
        if task_config is None:
            if task_type not in ALL_TASK_CONFIG:
                task_type = 'classification'
            task_config = get_task_config_by_app_type(task_type, app_type)
            if task_config is None:
                # 如果还是找不到，使用 ALL_TASK_CONFIG 作为后备
                task_config = ALL_TASK_CONFIG.get(task_type, ALL_TASK_CONFIG['classification'])
        
        env_var = task_config['env_var']
        
        # 对于视觉任务（CV），模板中同时包含 MODEL_PATH 和 SEGMENT_MODEL_PATH
        # 需要：1. 替换当前任务类型的占位符 2. 删除其他任务类型的环境变量行
        if app_type == 'cv' or (app_type is None and task_type in CV_TASK_CONFIG):
            # 替换当前任务类型的环境变量占位符
            placeholder = f'{{{{{env_var}_IN_CONTAINER}}}}'
            if model_path_in_container:
                if placeholder in compose_content:
                    compose_content = compose_content.replace(placeholder, model_path_in_container)
                    print(f'✅ 视觉任务：已替换 {env_var} 占位符 -> {model_path_in_container}')
                else:
                    print(f'⚠️  警告: 模板中未找到占位符 {placeholder}')
            else:
                print(f'⚠️  警告: model_path_in_container 为空，无法替换 {placeholder}')
            
            # 删除其他视觉任务类型的环境变量行
            # 分类任务：删除 SEGMENT_MODEL_PATH 行
            # 分割任务：删除 MODEL_PATH 行
            for other_task_type, other_config in CV_TASK_CONFIG.items():
                if other_task_type != task_type:
                    other_env_var = other_config['env_var']
                    # 删除包含其他任务类型环境变量的整行（包括占位符和已替换的值）
                    compose_content = re.sub(rf'^\s*-\s*{other_env_var}=.*\n', '', compose_content, flags=re.MULTILINE)
                    print(f'✅ 视觉任务：已删除其他任务类型的环境变量 {other_env_var}')
        
        # 如果是数值任务，替换 volumes 中的 {{MODEL_PATH}} 为 ../save_model，并修复环境变量名
        elif app_type == 'data' or (app_type is None and task_type in NUMERIC_TASK_CONFIG):
            # 替换当前任务类型的环境变量占位符
            placeholder = f'{{{{{env_var}_IN_CONTAINER}}}}'
            if model_path_in_container:
                compose_content = compose_content.replace(placeholder, model_path_in_container)
                print(f'✅ 数值任务：已替换 {env_var} 占位符 -> {model_path_in_container}')
            
            # 替换 volumes 配置中的 {{MODEL_PATH}} 为 ../save_model
            compose_content = compose_content.replace('{{MODEL_PATH}}', '../save_model')
            # 数值任务需要使用 APP_PORT 而不是 PORT（Dockerfile 中定义的是 APP_PORT）
            compose_content = compose_content.replace('- PORT=', '- APP_PORT=')
            print(f'✅ 数值任务：已配置 volumes 映射 ../save_model -> /app/save_model:ro')
            print(f'✅ 数值任务：已修复环境变量 PORT -> APP_PORT')
        
        print(f'✅ 已配置 {env_var}，删除其他任务类型的环境变量')
        
        # 处理GPU相关配置
        if use_gpu == "true":
            # GPU模式：替换GPU相关占位符
            if '{{USE_GPU}}' in compose_content:
                compose_content = compose_content.replace('{{USE_GPU}}', use_gpu)
                print(f'✅ 已配置 USE_GPU={use_gpu}')
            
            if '{{GPU_DEVICE_IDS}}' in compose_content:
                compose_content = compose_content.replace('{{GPU_DEVICE_IDS}}', gpu_device_ids)
                print(f'✅ 已配置 GPU_DEVICE_IDS={gpu_device_ids}')
            
            if '{{CUDA_VISIBLE_DEVICES}}' in compose_content:
                compose_content = compose_content.replace('{{CUDA_VISIBLE_DEVICES}}', cuda_visible_devices)
                print(f'✅ 已配置 CUDA_VISIBLE_DEVICES={cuda_visible_devices}')
        else:
            # CPU模式：删除GPU相关的配置字段
            # 删除环境变量：CUDA_VISIBLE_DEVICES、USE_GPU、GPU_DEVICE_IDS
            compose_content = re.sub(r'^\s*-\s*CUDA_VISIBLE_DEVICES=.*\n', '', compose_content, flags=re.MULTILINE)
            compose_content = re.sub(r'^\s*-\s*USE_GPU=.*\n', '', compose_content, flags=re.MULTILINE)
            compose_content = re.sub(r'^\s*-\s*GPU_DEVICE_IDS=.*\n', '', compose_content, flags=re.MULTILINE)
            
            # 删除deploy配置块中的device_ids字段
            # 删除包含device_ids的行
            compose_content = re.sub(r'^\s*device_ids:.*\n', '', compose_content, flags=re.MULTILINE)
            
            print(f'✅ CPU模式：已删除GPU相关配置字段（CUDA_VISIBLE_DEVICES、USE_GPU、GPU_DEVICE_IDS、device_ids）')
        
        compose_file_path = Path(compose_file)
        with open(compose_file_path, 'w', encoding='utf-8') as f:
            f.write(compose_content)
        
        print(f'✅ 已生成 docker-compose 文件: {compose_file}')
        return True
    except Exception as e:
        print(f'❌ 生成 docker-compose 文件失败: {e}')
        return False


def check_and_build_image(image_name, dockerfile, code_dir, service_id):
    """
    检查并构建Docker镜像
    参数:
        image_name: 镜像名称
        dockerfile: Dockerfile文件名
        code_dir: 代码目录路径（deploy_code_cv 或 deploy_code_data）
        service_id: 服务ID
    返回: True 成功, False 失败
    """
    try:
        # 使用两种方法检查镜像是否存在
        check_image_cmd1 = ['docker', 'image', 'inspect', image_name]
        result1 = subprocess.run(check_image_cmd1, capture_output=True, text=True, check=False)
        image_exists_by_inspect = result1.returncode == 0
        
        image_name_without_tag = image_name.split(':')[0]
        check_image_cmd2 = ['docker', 'images', '--format', '{{.Repository}}:{{.Tag}}', image_name_without_tag]
        result2 = subprocess.run(check_image_cmd2, capture_output=True, text=True, check=False)
        
        image_exists_by_list = False
        existing_images_list = []
        if result2.returncode == 0 and result2.stdout:
            existing_images_list = [img.strip() for img in result2.stdout.strip().split('\n') if img.strip()]
            image_exists_by_list = image_name in existing_images_list
            if not image_exists_by_list and existing_images_list:
                image_exists_by_list = any(img.startswith(image_name_without_tag + ':') 
                                           for img in existing_images_list)
        
        image_exists = image_exists_by_inspect or image_exists_by_list
        
        print(f'🔍 检查镜像: {image_name}')
        print(f'   方法1 (inspect): {"存在" if image_exists_by_inspect else "不存在"}')
        print(f'   方法2 (images): {"存在" if image_exists_by_list else "不存在"} (找到: {", ".join(existing_images_list) if existing_images_list else "无"})')
        print(f'   最终结果: {"存在" if image_exists else "不存在"}')
        
        if not image_exists:
            dockerfile_path = code_dir / dockerfile
            if not dockerfile_path.exists():
                print(f'❌ Dockerfile不存在: {dockerfile_path}')
                return False
            
            print(f'🔨 构建Docker镜像: {image_name}（使用{dockerfile_path}）...')
            build_cmd = ['docker', 'build', '-t', image_name, '-f', str(dockerfile_path), '.']
            process = subprocess.Popen(build_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 
                                     text=True, bufsize=1, universal_newlines=True)
            build_output = []
            for line in process.stdout:
                line = line.rstrip()
                print(line)
                build_output.append(line)
            returncode = process.wait()
            if returncode != 0:
                error_msg = '\n'.join(build_output)
                print(f'❌ 镜像构建失败')
                return False
            print(f'✅ 镜像构建完成')
        else:
            print(f'✅ 镜像已存在: {image_name}')
        
        return True
    except Exception as e:
        print(f'❌ 检查/构建镜像失败: {e}')
        return False


def stop_old_container(container_name, compose_file):
    """
    停止旧容器（如果存在）
    """
    try:
        check_container_cmd = ['docker', 'ps', '-a', '--format', '{{.Names}}']
        result = subprocess.run(check_container_cmd, capture_output=True, text=True, check=False)
        if container_name in result.stdout:
            print(f'⚠️  停止旧容器: {container_name}')
            subprocess.run(['docker-compose', '-f', compose_file, 'down'], 
                         capture_output=True, check=False)
            subprocess.run(['docker', 'stop', container_name], capture_output=True, check=False)
            subprocess.run(['docker', 'rm', container_name], capture_output=True, check=False)
    except Exception as e:
        print(f'⚠️  清理旧容器时出错: {e}')


def start_docker_service(compose_file, container_name, port, task_type, service_id, app_type=None):
    """
    启动Docker服务
    参数:
        app_type: 应用类型，'cv' 表示视觉任务，'data' 表示数值任务
    返回: (success, return_info) 成功返回 (True, return_info), 失败返回 (False, return_info)
    """
    try:
        print(f'🚀 启动Docker服务...')
        start_cmd = ['docker-compose', '-f', compose_file, 'up', '-d']
        start_result = subprocess.run(start_cmd, capture_output=True, text=True, check=False)
        
        if start_result.returncode != 0:
            print(f'❌ 服务启动失败: {start_result.stderr}')
            return_info = CustomResponse(status=500,
                                         data={'service_id': service_id, 'flag': False},
                                         suffix=f'服务启动失败: {start_result.stderr}').result_data
            return False, return_info
        
        print(f'⏳ 等待服务启动...')
        time.sleep(10)
        
        # 检查运行中的容器
        check_running_cmd = ['docker', 'ps', '--format', '{{.Names}}']
        result = subprocess.run(check_running_cmd, capture_output=True, text=True, check=False)
        if container_name in result.stdout:
            print(f'✅ 服务启动成功！')
            # 根据 appType 获取对应的任务配置
            task_config = get_task_config_by_app_type(task_type, app_type)
            
            # 如果找不到配置，使用 ALL_TASK_CONFIG 作为后备
            if task_config is None:
                task_config = ALL_TASK_CONFIG.get(task_type, ALL_TASK_CONFIG['classification'])
            api_path = task_config['api_path']
            api_name = task_config['api_name']
            server_url = f'http://{get_address()}:{port}{api_path}'
            print(f'🌐 {api_name}服务 URL: {server_url}')
            return_info = CustomResponse(status=200,
                                         data={'service_id': service_id,
                                               'url': server_url,
                                               'type': 'post',
                                               'flag': True,
                                               'task_type': task_type}).result_data
            return True, return_info
        else:
            # 容器未运行，检查容器状态和日志
            print(f'❌ 服务启动后容器未运行，开始诊断...')
            
            # 检查所有容器（包括已停止的）
            check_all_cmd = ['docker', 'ps', '-a', '--filter', f'name={container_name}', '--format', '{{.Names}} | {{.Status}}']
            all_result = subprocess.run(check_all_cmd, capture_output=True, text=True, check=False)
            
            error_details = []
            if container_name in all_result.stdout:
                # 容器存在但已停止
                container_status = all_result.stdout.strip()
                error_details.append(f'容器状态: {container_status}')
                
                # 获取容器日志
                print(f'📋 获取容器日志...')
                logs_cmd = ['docker', 'logs', '--tail', '50', container_name]
                logs_result = subprocess.run(logs_cmd, capture_output=True, text=True, check=False)
                if logs_result.stdout or logs_result.stderr:
                    logs_output = logs_result.stdout + logs_result.stderr
                    error_details.append(f'容器日志:\n{logs_output}')
                    print(f'容器日志:\n{logs_output}')
            else:
                error_details.append('容器不存在')
            
            # 检查docker-compose启动输出
            if start_result.stdout:
                error_details.append(f'docker-compose输出: {start_result.stdout}')
            if start_result.stderr:
                error_details.append(f'docker-compose错误: {start_result.stderr}')
            
            error_msg = '\n'.join(error_details) if error_details else '服务启动后容器未运行'
            print(f'❌ 诊断信息: {error_msg}')
            
            return_info = CustomResponse(status=500,
                                         data={'service_id': service_id, 'flag': False},
                                         suffix=error_msg).result_data
            return False, return_info

    except subprocess.CalledProcessError as e:
        print(f'❌ Docker启动失败')
        if e.stderr:
            print(f'error:{e.stderr}')
        return_info = CustomResponse(status=500,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=f'Docker启动异常: {e.stderr}').result_data
        return False, return_info
    except Exception as e:
        print(f'❌ Docker启动异常: {e}')
        return_info = CustomResponse(status=500,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=f'Docker启动异常: {e}').result_data
        return False, return_info


def start_server(data):
    """
    start server, 启动服务
    """
    # 获取参数
    task_type = data.get('taskType', 'classification')
    app_type = data.get('appType', None)  # 获取 appType 参数，用于区分 cv 或 data
    service_id = data.get('serviceId', '')
    service_tag = data.get('serviceTag', '')
    model_url = data.get('modelUrl', '')
    py_url = data.get('pyUrl', '')
    port = data.get('port', '')
    cuda_device = data.get('cuda', '')  # 获取cuda参数，如 "cuda:0", "cuda:1" 等
    
    # 根据 appType 获取对应的任务配置
    task_config = get_task_config_by_app_type(task_type, app_type)
    
    # 如果找不到配置，尝试使用默认值
    if task_config is None:
        if task_type not in ALL_TASK_CONFIG:
            print(f'⚠️  警告: taskType={task_type} 无效，使用默认值 classification')
            task_type = 'classification'
        task_config = get_task_config_by_app_type(task_type, app_type)
        if task_config is None:
            # 如果还是找不到，使用 ALL_TASK_CONFIG 作为后备
            task_config = ALL_TASK_CONFIG.get(task_type, ALL_TASK_CONFIG['classification'])
    
    task_type_name = task_config['name']
    app_type_str = f" ({app_type})" if app_type else ""
    print(f'📋 任务类型: {task_type} ({task_type_name}){app_type_str}')

    folder = service_id + '-' + str(port)
    print(folder)

    # 检查端口是否被占用
    res = is_port_in_use(port)
    print(f'res:{res}')

    # 端口被占用的情况
    if res:
        return_info = CustomResponse(status=204,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=f'{port}端口被占用').result_data
        print('回调写入:', return_info)
        return_info = submit_api(service_tag, return_info, task_type, app_type)
        return return_info

    # 端口可用的情况
    try:
        # 1. 下载文件（传入task_type和app_type以确定下载路径）
        py_path, model_path = download_files(py_url, model_url, folder, service_id, service_tag, task_type, app_type)
        if not py_path or not model_path:
            return_info = CustomResponse(status=204,
                                         data={'service_id': service_id, 'flag': False},
                                         suffix='未成功获取部署文件').result_data
            print('回调写入:', return_info)
            return_info = submit_api(service_tag, return_info, task_type, app_type)
            return return_info

        # 2. 准备目录（传入task_type和app_type以确定代码目录）
        dir_result = prepare_directories(folder, service_id, task_type, app_type)
        if dir_result is None:
            return_info = CustomResponse(status=500,
                                         data={'service_id': service_id, 'flag': False},
                                         suffix='目录不存在').result_data
            print('回调写入:', return_info)
            return_info = submit_api(service_tag, return_info, task_type, app_type)
            return return_info
        
        server_dir, code_dir, original_cwd = dir_result

        # 3. 查找模型文件（传入task_type和app_type以确定查找的文件类型）
        model_path_in_container = find_model_file(model_path, server_dir, service_id, task_type, app_type)
        if model_path_in_container is None:
            return_info = CustomResponse(status=500,
                                         data={'service_id': service_id, 'flag': False},
                                         suffix='未找到模型文件或路径计算错误').result_data
            print('回调写入:', return_info)
            return_info = submit_api(service_tag, return_info, task_type, app_type)
            return return_info

        # 4. Docker配置
        image_name, container_name, compose_file, compose_template, dockerfile = get_docker_config(port, task_type, app_type)
        ssh_host_port = 4022 + port - 8796
        
        # 从任务配置中获取环境变量名（task_config 已在上面获取）
        model_env_var = task_config['env_var']
        
        # 解析cuda参数，生成GPU配置
        use_gpu, gpu_device_ids, cuda_visible_devices = parse_cuda_device(cuda_device)
        
        print(f'🐳 Docker配置 ({DEPLOY_MODE}模式):')
        print(f'  - 镜像: {image_name}')
        print(f'  - 容器名: {container_name}')
        print(f'  - 端口: {port}')
        print(f'  - SSH端口: {ssh_host_port}')
        print(f'  - 模型路径: {model_path_in_container}')
        print(f'  - 环境变量: {model_env_var}={model_path_in_container}')
        print(f'  - Dockerfile: {dockerfile}')
        if cuda_device:
            print(f'  - CUDA设备: {cuda_device}')
            print(f'  - GPU设备ID: {gpu_device_ids}')
            print(f'  - CUDA_VISIBLE_DEVICES: {cuda_visible_devices}')

        # 5. 生成docker-compose文件
        if not generate_compose_file(compose_template, compose_file, image_name, container_name,
                                     port, ssh_host_port, model_path_in_container, task_type, service_id,
                                     use_gpu, gpu_device_ids, cuda_visible_devices, app_type):
            return_info = CustomResponse(status=500,
                                         data={'service_id': service_id, 'flag': False},
                                         suffix='生成配置文件失败').result_data
            print('回调写入:', return_info)
            return_info = submit_api(service_tag, return_info, task_type, app_type)
            return return_info

        # 6. 检查并构建镜像
        if not check_and_build_image(image_name, dockerfile, code_dir, service_id):
            return_info = CustomResponse(status=500,
                                         data={'service_id': service_id, 'flag': False},
                                         suffix='镜像操作失败').result_data
            print('回调写入:', return_info)
            return_info = submit_api(service_tag, return_info, task_type, app_type)
            return return_info

        # 7. 停止旧容器
        stop_old_container(container_name, compose_file)

        # 8. 启动Docker服务
        success, return_info = start_docker_service(compose_file, container_name, port, task_type, service_id, app_type)
        print('回调写入:', return_info)
        return_info = submit_api(service_tag, return_info, task_type, app_type)
        return return_info

    except Exception as e:
        print(traceback.format_exc())
        print(f'❌ server failure str({e})')
        return_info = CustomResponse(status=500,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=str(e)).result_data
        print(return_info)
        print('回调写入:', return_info)
        return_info = submit_api(service_tag, return_info, task_type, app_type)
        return return_info


def get_service_directories(service_id, port):
    """
    获取服务目录路径，自动检测代码目录（deploy_code_cv 或 deploy_code_data）
    返回: (service_dir, code_dir) 或 None 如果目录不存在
    """
    folder = service_id + '-' + str(port)
    service_dir = Path(__file__).parent / 'server' / folder
    
    # 尝试查找常见的代码目录
    possible_code_dirs = ['deploy_code_cv', 'deploy_code_data']
    for code_dir_name in possible_code_dirs:
        code_dir = service_dir / code_dir_name
        if code_dir.exists():
            return service_dir, code_dir
    
    # 如果都不存在，默认返回 deploy_code_cv（向后兼容）
    code_dir = service_dir / 'deploy_code_cv'
    return service_dir, code_dir


def find_compose_file(port, code_dir):
    """
    在代码目录中查找docker-compose文件
    参数:
        port: 端口号
        code_dir: 代码目录路径（deploy_code_cv 或 deploy_code_data）
    返回: compose_file路径 或 None 如果未找到
    """
    if not os.path.exists(code_dir):
        return None
    
    code_path = Path(code_dir).resolve()
    pattern = f'docker-compose*.{port}.yml'
    # 使用绝对路径进行glob搜索
    matches = list(code_path.glob(pattern))
    
    if matches:
        compose_file = matches[0]
        if compose_file.exists():
            return str(compose_file.resolve())
    return None


def stop_docker_container(compose_file, service_id):
    """
    停止Docker容器
    返回: (success, return_info) 成功返回 (True, return_info), 失败返回 (False, return_info)
    
    注意：只停止和删除指定的容器，不会影响其他服务。即使多个服务共享同一个Docker网络，
    也只停止当前服务的容器，不会删除网络或其他服务的容器。
    """
    if not compose_file or not Path(compose_file).exists():
        return_info = CustomResponse(status=204,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=f'未找到 docker-compose 文件: {compose_file}').result_data
        return False, return_info
    
    print(f'🛑 停止服务: {compose_file}')
    
    # 获取compose文件中定义的容器名称
    try:
        with open(compose_file, 'r', encoding='utf-8') as f:
            compose_content = f.read()
        # 尝试从compose文件中提取容器名（如果定义了container_name）
        container_name_match = re.search(r'container_name:\s*([^\s]+)', compose_content)
        expected_container_name = container_name_match.group(1) if container_name_match else None
    except Exception as e:
        print(f'⚠️  读取compose文件时出错: {e}')
        expected_container_name = None
    
    if not expected_container_name:
        # 如果无法从compose文件中获取容器名，尝试使用docker-compose ps获取
        ps_cmd = ['docker-compose', '-f', compose_file, 'ps', '-q']
        ps_result = subprocess.run(ps_cmd, capture_output=True, text=True, check=False)
        if ps_result.returncode == 0 and ps_result.stdout.strip():
            container_id = ps_result.stdout.strip().split('\n')[0]
            # 通过容器ID获取容器名
            inspect_cmd = ['docker', 'inspect', '--format', '{{.Name}}', container_id]
            inspect_result = subprocess.run(inspect_cmd, capture_output=True, text=True, check=False)
            if inspect_result.returncode == 0:
                expected_container_name = inspect_result.stdout.strip().lstrip('/')
        
        if not expected_container_name:
            return_info = CustomResponse(status=204,
                                         data={'service_id': service_id, 'flag': False},
                                         suffix=f'无法确定容器名称').result_data
            return False, return_info
    
    print(f'📦 目标容器: {expected_container_name}')
    
    # 先检查容器是否存在
    check_cmd = ['docker', 'ps', '-a', '--format', '{{.Names}}']
    check_result = subprocess.run(check_cmd, capture_output=True, text=True, check=False)
    container_exists = expected_container_name in check_result.stdout
    
    if not container_exists:
        # 容器不存在，直接返回成功
        print(f'✅ 容器不存在，无需停止: {expected_container_name}')
        return_info = CustomResponse(status=200,
                                     data={'service_id': service_id, 'flag': True}).result_data
        return True, return_info
    
    # 直接使用docker命令停止和删除指定的容器，不影响其他服务
    # 第一步：停止容器
    stop_cmd = ['docker', 'stop', expected_container_name]
    stop_result = subprocess.run(stop_cmd, capture_output=True, text=True, check=False)
    
    # 如果停止失败且不是因为容器不存在，记录错误
    if stop_result.returncode != 0 and 'No such container' not in stop_result.stderr:
        print(f'⚠️  停止容器时出现警告: {stop_result.stderr}')
    
    # 第二步：删除容器
    rm_cmd = ['docker', 'rm', expected_container_name]
    rm_result = subprocess.run(rm_cmd, capture_output=True, text=True, check=False)
    
    # 验证容器是否已成功删除
    check_cmd = ['docker', 'ps', '-a', '--format', '{{.Names}}']
    check_result = subprocess.run(check_cmd, capture_output=True, text=True, check=False)
    container_still_exists = expected_container_name in check_result.stdout
    
    # 判断操作是否成功
    if not container_still_exists:
        print(f'✅ 服务已停止: {expected_container_name}')
        return_info = CustomResponse(status=200,
                                     data={'service_id': service_id, 'flag': True}).result_data
        return True, return_info
    else:
        # 容器仍然存在，说明删除失败
        error_msg = rm_result.stderr if rm_result.stderr else stop_result.stderr
        print(f'⚠️  停止服务时出现错误: {error_msg}')
        return_info = CustomResponse(status=204,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=f'服务停止失败: {error_msg}').result_data
        return False, return_info




def remove_service_directory(service_dir, service_id):
    """
    删除服务目录及其所有内容
    返回: (success, return_info) 成功返回 (True, return_info), 失败返回 (False, return_info)
    """
    if not os.path.exists(service_dir):
        print(f"⚠️ 文件夹 '{service_dir}' 不存在，无需删除")
        return_info = CustomResponse(status=200,
                                     data={'service_id': service_id, 'flag': True}).result_data
        return True, return_info
    
    try:
        print(f'🗑️  删除文件夹: {service_dir}')
        shutil.rmtree(service_dir)
        print(f"✅ 文件夹 '{service_dir}' 已成功删除!")
        return_info = CustomResponse(status=200,
                                     data={'service_id': service_id, 'flag': True}).result_data
        return True, return_info
    except Exception as e:
        print(f'❌ 删除文件夹时出错: {str(e)}')
        return_info = CustomResponse(status=500,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=str(e)).result_data
        return False, return_info


def stop_server(data):
    """
    stop server, 停止服务
    """
    service_id = data.get('serviceId', '')
    service_tag = data.get('serviceTag', '')
    port = data.get('port', '')
    task_type = data.get('taskType', 'classification')  # 获取任务类型，默认为 classification
    app_type = data.get('appType', None)  # 获取 appType 参数，用于区分 cv 或 data
    
    try:
        # 1. 获取服务目录（自动检测代码目录）
        service_dir, code_dir = get_service_directories(service_id, port)
        folder = service_id + '-' + str(port)
        print(folder)
        
        if not os.path.exists(service_dir):
            raise FileNotFoundError(f'没有{service_id}-{port}目录！')
        
        if not os.path.exists(code_dir):
            code_dir_name = code_dir.name
            raise FileNotFoundError(f'没有{service_id}-{port}/{code_dir_name}目录！')

        # 2. 查找 docker-compose 文件
        compose_file = find_compose_file(port, code_dir)
        
        if compose_file is None:
            print(f'⚠️  未找到 docker-compose 文件（端口: {port}）')
            return_info = CustomResponse(status=204,
                                         data={'service_id': service_id, 'flag': False},
                                         suffix=f'未找到对应的 docker-compose 配置文件').result_data
        else:
            # 3. 切换到代码目录并停止容器
            original_cwd = os.getcwd()
            try:
                os.chdir(code_dir)
                print(f'current directory: {os.getcwd()}')
                # 4. 停止容器
                success, return_info = stop_docker_container(compose_file, service_id)
            finally:
                # 恢复原始工作目录
                os.chdir(original_cwd)

    except Exception as e:
        print(f'❌ 停止服务异常: {e}')
        return_info = CustomResponse(status=500,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=str(e)).result_data
    
    print('回调写入:', return_info)
    return_info = submit_api(service_tag, return_info, task_type, app_type)
    return return_info


def delete_server(data):
    """
    delete server, 删除服务
    """
    service_id = data.get('serviceId', '')
    service_tag = data.get('serviceTag', '')
    port = data.get('port', '')
    task_type = data.get('taskType', 'classification')  # 获取任务类型，默认为 classification
    app_type = data.get('appType', None)  # 获取 appType 参数，用于区分 cv 或 data
    
    try:
        # 1. 获取服务目录（自动检测代码目录）
        service_dir, code_dir = get_service_directories(service_id, port)
        folder = service_id + '-' + str(port)
        print("folder:", folder)
        
        # 如果目录不存在，直接返回成功
        if not os.path.exists(service_dir):
            return_info = CustomResponse(status=200,
                                         data={'service_id': service_id, 'flag': True}).result_data
            print('回调写入:', return_info)
            return_info = submit_api(service_tag, return_info, task_type, app_type)
            return return_info
        
        # 2. 删除 Docker 容器（如果存在）
        compose_file = find_compose_file(port, code_dir)
        if compose_file:
            original_cwd = os.getcwd()
            try:
                os.chdir(code_dir)
                print(f'current directory: {os.getcwd()}')
                print(f'🗑️  删除 Docker 容器...')
                # 使用 stop_docker_container 停止并删除容器
                success, _ = stop_docker_container(compose_file, service_id)
                if not success:
                    print(f'⚠️  删除容器时出现警告，继续删除文件夹')
            except Exception as e:
                print(f'⚠️  删除容器时出错: {e}，继续删除文件夹')
            finally:
                os.chdir(original_cwd)
        else:
            print(f'⚠️  未找到 docker-compose 文件，跳过容器删除步骤')
        
        # 3. 删除服务目录
        success, return_info = remove_service_directory(service_dir, service_id)
        
    except Exception as e:
        print(f'❌ 删除服务异常: {e}')
        return_info = CustomResponse(status=500,
                                     data={'service_id': service_id, 'flag': False},
                                     suffix=str(e)).result_data
    
    print('回调写入:', return_info)
    return_info = submit_api(service_tag, return_info, task_type, app_type)
    return return_info


def async_func(func_method, request_data):
    """
    异步处理
    """
    # 定义 executor
    executor = ProcessPoolExecutor(max_workers=1)
    try:
        # 异步执行
        executor.submit(func_method, request_data)
        resp = {'status': 200, 'message': '请求已收到！', 'data': {}}
    except Exception as e:
        print(e)
        resp = {'status': 400, 'message': '请求异常！', 'data': {}}
    # 关闭
    executor.shutdown(wait=False)
    return resp


@app.route('/start/', methods=['POST'])
def start():
    # 获取用户请求数据
    request_data = request.get_json()
    print(request_data)
    resp = async_func(start_server, request_data)
    print(resp)
    return jsonify(resp)


@app.route('/stop/', methods=['POST'])
def stop():
    # 获取用户请求数据
    request_data = request.get_json()
    print(request_data)
    resp = async_func(stop_server, request_data)
    return jsonify(resp)


@app.route('/delete/', methods=['POST'])
def delete():
    # 获取用户请求数据
    request_data = request.get_json()
    print(request_data)
    resp = async_func(delete_server, request_data)
    return jsonify(resp)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='machine learning model')
    parser.add_argument('--port', type=int, default=8051, help='port, default 8051')
    args = parser.parse_args()
    app.run(host='0.0.0.0', port=args.port, debug=False, threaded=True)
